#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <sys/ioctl.h>
#include <poll.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#include "./include/common.h"

#define BUFSIZE      (32) /* 设置最大偏移量为64, 方便打印完整的内存空间数据*/
#define NETLINK_TEST (30)
#define USER_PORT    (100)
#define MAX_PLOAD    (125)
#define MSG_LEN      (125)

typedef struct __user_msg_info
{
    struct nlmsghdr hdr;
    char msg[MSG_LEN];
} user_msg_info;

/**
 * @brief  usage_info()
 * @note   打印帮助信息
 * @param [in]
 * @param [out]
 * @retval 
 */
void usage_info(void)
{
	printf("\n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");
	printf("+       help information  @sumu          +\n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");

	printf("help:\n");
	printf("use format: ./app_name /dev/device_name arg1 ... \n");
	printf("            ./app_demo.out /dev/sdevice 1 x x    # 从x位置读取x字节 \n");
    printf("            驱动中buf最大为32字节 \n");
	printf("\n");

	printf("command info:\n");
	printf("  (1)load module  : insmod module_name.ko\n");
	printf("  (2)unload module: rmmod module_name.ko\n");
	printf("  (3)show module  : lsmod\n");
	printf("  (4)view device  : cat /proc/devices\n");
	printf("  (5)create device node: mknod /dev/device_name c major_num secondary_num \n");
	printf("  (6)show device node  : ls /dev/device_name \n");
	printf("  (7)show device vlass  : ls /sys/class \n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");
}

void arg_info_print(int argc, char *argv[])
{
    int i = 0;
    PRT("total arg is %d: ", argc);
    for (i = 0; i < argc; i++) 
    {
        printf("argv[%d]=%s ", i, argv[i]);
    }
    printf("\n");
}

// ./xxx.out /dev/sdevice 1 x x
static int func_demo1(int argc, char *argv[])
{
    int read_fd = -1;
    int ret = 0;
    unsigned int i = 0;
    char *dev_name = NULL;
    unsigned int off_end = 0; // 定义读写偏移位置
    unsigned int offset = 0;
    unsigned int read_size = 0;
    char readbuf[BUFSIZE] = {0};

    // 获取设备节点名称
    if(argv[1] == NULL)
    {
        return -1;
    }
    dev_name = argv[1]; // 设备节点名称

    /** 打开设备文件 */
    read_fd = open(dev_name,  O_RDWR); // 使用阻塞标志
    if (read_fd < 0)
    {
        PRTE("can't open file %s !\n", dev_name);
        return -1;
    }

    off_end = lseek(read_fd, 0, SEEK_END); // 读取字符设备文件可读写最大的大小
    offset = atoi(argv[3]);// 获取读写的偏移位置
    if(offset > off_end)
    {
        offset = off_end;
    }

    read_size = atoi(argv[4]);// 获取要读取的字节数
    if((read_size + offset > off_end) || (read_size == 0))
    {
        read_size = off_end - offset;
    }
    //PRT("%s max buffer size is %d, offset=%d, read_size=%d\n", dev_name, off_end, offset, read_size);
    do 
    {
        ret = lseek(read_fd, offset, SEEK_SET); // 将偏移量设置为距离起始地址 off的位置
        if (ret < 0)
        {
            PRTE("lseek failed!\n");
            return ret;;
        }
        memset(readbuf, 0, sizeof(readbuf));
        ret = read(read_fd, readbuf, read_size);
        if (ret < 0)
        {
            PRTE("read from %d failed!\n", read_fd);
        }
        else
        {
            /*  读取成功，打印出读取成功的数据 */
            PRT("read data from %s!", dev_name);
            for(i = 0; i < read_size; i++)
            {
                printf("0x%x ", readbuf[i]);
            }
            printf("\n");
        }
    } while(0);

    /* 关闭设备 */
    ret = close(read_fd);
    if (ret < 0)
    {
        PRTE("can't close file %s !\n", dev_name);
        return -1;
    }

    return 0;
}

// ./xxx.out /dev/sdevice 1 x x
static int func_demo4(int argc, char *argv[])
{
    int ret = 0;
    int len = 0;
    int i = 0;
    int socket_fd = -1;
    struct sockaddr_nl nl = {0};         // 定义一个指向 struct sockaddr_nl 结构体变量
    char buf[4096] = {0};          // 数据接收缓冲区

    nl.nl_family = AF_NETLINK;            // 设置 nl 结构体的 nl_family 字段为 AF_NETLINK， 指定地址族为Netlink
    nl.nl_pid = 0;                        // 设置 nl 结构体的 nl_pid 字段为 0， 表示目标进程 ID 为 0， 即广播给所有进程
    nl.nl_groups = 1;                     // 设置 nl 结构体的 nl_groups 字段为 1， 表示只接收基本组的内核事件
    // 创建一个 Netlink套接字
    socket_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT); 
    if (socket_fd < 0) {
        PRTE("socket error\n");
        return -1;
    }
    // 使用 bind 函数将 socket_fd 套接字与 nl 地址结构体绑定在一起 
    ret = bind(socket_fd, (struct sockaddr *)&nl, sizeof(struct sockaddr_nl)); 
    if (ret < 0)
    {
        PRTE("bind error!\n");
        return -1;
    }
    while (1)
    {
        bzero(buf, 4096);                     // 将缓冲区 buf 清零， 确保数据接收前的初始化
        len = recv(socket_fd, &buf, 4096, 0); // 从 socket_fd 套接字接收数据， 存储到缓冲区 buf 中，最大接收字节数为 4096
        for (i = 0; i < len; i++)
        {
            if (*(buf + i) == '\0')
            { 
                // 如果接收到的数据中有 '\0' 字符， 将其替换为 '\n'， 以便在打印时换行显示
                buf[i] = '\n';
            }
        }
        PRT("hotplug event: \"%s\"\n", buf); // 打印接收到的数据
    }
    return 0;
}

static int func_demo5(int argc, char *argv[])
{
    int skfd = -1;
    int ret = 0;
    user_msg_info u_info = {0};
    socklen_t len = 0;
    struct nlmsghdr *nlh = NULL;
    struct sockaddr_nl saddr, daddr;
    char *umsg = "hello netlink!!";
    int loop_count = 0;

    /*Create netlink socket*/
    skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); // Create a socket using user defined protocol NETLINK_TEST.
    if (skfd == -1)
    {
        perror("create socket error\n");
        return -1;
    }

    // Source address.
    memset(&saddr, 0, sizeof(saddr));
    saddr.nl_family = AF_NETLINK; // AF_NETLINK
    saddr.nl_pid = USER_PORT;     // netlink portid, same as kernel.
    saddr.nl_groups = 0;
    if (bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) // bind to skfd with saddr.
    {
        perror("bind() error\n");
        close(skfd);
        return -1;
    }

    // Destination address.
    memset(&daddr, 0, sizeof(daddr));
    daddr.nl_family = AF_NETLINK;
    daddr.nl_pid = 0; // to kernel
    daddr.nl_groups = 0;

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
    memset(nlh, 0, sizeof(struct nlmsghdr));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
    nlh->nlmsg_flags = 0;
    nlh->nlmsg_type = 0;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = saddr.nl_pid; // self port

    memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
    while (loop_count < 5)
    {
        printf("sendto kernel:%s\n", umsg);
        ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
        if (!ret)
        {
            perror("sendto error\n");
            close(skfd);
            exit(-1);
        }

        // Receive netlink message from kernel.
        memset(&u_info, 0, sizeof(u_info));
        len = sizeof(struct sockaddr_nl);
        ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
        if (!ret)
        {
            perror("recv form kernel error\n");
            close(skfd);
            exit(-1);
        }

        printf("from kernel:%s\n", u_info.msg);
        usleep(1000 * 1000 * 3);
        loop_count++;
    }
    close(skfd);

    free((void *)nlh);
    return 0;
}

int main(int argc, char *argv[])
{
    unsigned int arg1 = 0;
    
    printf("*** Build Time: %s %s,Git Version: %s Git Remote: %s***\n", 
            __DATE__, __TIME__, GIT_VERSION, GIT_PATH);
    /** 
     * 获取传入参数并打印 
     * ./xxx.out /dev/sdevice x x x
     */
    arg_info_print(argc, argv);
    if (argc < 3) 
    {
        usage_info();
        return -1;
    }

    /** 解析参数 */
    arg1 = atoi(argv[2]);  // 1=读，2=写，3=ioctl

    if (arg1 == 1) /** 从设备节点指定位置读取数据 */
    {
        func_demo1(argc, argv);
    }
    else if (arg1 == 4) /** netlink */
    {
        func_demo4(argc, argv);
    }
    else if (arg1 == 5) /** netlink */
    {
        func_demo5(argc, argv);
    }

    return 0;
}

/**
 * @brief  sys_pts_get_ms
 * @note   获取系统时间戳 单位 ms
 * @param [in]
 * @param [out]
 * @retval 
 */
int sys_pts_get_ms(void)
{
    struct timespec tv = {0};
    long long lasttime = 0;

    clock_gettime(CLOCK_MONOTONIC, &tv);
    lasttime = tv.tv_sec * 1000 + tv.tv_nsec / (1000 * 1000);

    return lasttime;

}